{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c4d4deb6",
   "metadata": {},
   "source": [
    "# Controlled-Z Pulse Calibration\n",
    "\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*\n",
    "\n",
    "## Outline\n",
    "\n",
    "This tutorial introduces how to use Quanlse to simulate the calibration for the controlled-Z (CZ) gate in real experiments. The outline of this tutorial is as follows:\n",
    "\n",
    "- Background\n",
    "- Preparation\n",
    "- Initialize the Two-qubit Simulator\n",
    "- Controlled-Z Gate Pulse Calibration\n",
    "    - Calibrate the Pulse for Single-qubit Gates\n",
    "    - Calibrate the Conditional-phase\n",
    "    - Calibrate the Dynamical-Phase\n",
    "- Generating Bell State Using Calibrated Pulses"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d02c4a71",
   "metadata": {},
   "source": [
    "## Background\n",
    "\n",
    "In the previous calibration tutorial, we have introduced the calibration and characterization methods for the single-qubit. In this tutorial, we will introduce the pulse calibration method for the controlled-Z (CZ) gate. In superconducting quantum computing, the CZ gate is a commonly used native two-qubit gate, which is easier to be implemented on the superconducting platform. The basic principle is to tune the eigenfrequency of qubits by adjusting the magnetic flux, so that the $|11\\rangle $ state and the $|20\\rangle$ ($|02\\rangle$) state resonate and undergo avoided crossing, and eventually accumulate the phase of $\\pi$ on the $|11\\rangle$ state. The role of the CZ gate can be understood as that the $|1\\rangle$ state phase of the target qubit increases by $\\pi$ when the control qubit is at the $|1\\rangle$ state. The corresponding matrix of the CZ gate in the two-qubit computational subspace is represented as:\n",
    "\n",
    "$$\n",
    "U_{\\rm CZ} = |0\\rangle\\langle 0| \\otimes I + |1\\rangle\\langle1| \\otimes \\hat{\\sigma}^z = \\begin{bmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & -1 \\end{bmatrix}. \n",
    "$$\n",
    "\n",
    "So far, Quanlse has provided the pulse optimization cloud service for the CZ gate, and introduced the relevant principles in the corresponding tutorial, which can be viewed in detail by users by clicking [Controlled-Z gate](https://quanlse.baidu.com/#/doc/tutorial-cz). In this tutorial, we will introduce the calibration method of CZ gates in real experiments."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0918f19d",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "\n",
    "After successfully installed Quanlse, you could run the Quanlse program below following this tutorial. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0568aa4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import the dependent packages\n",
    "import numpy\n",
    "from math import pi\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Import the two-qubit simulator\n",
    "from Quanlse.Superconduct.Simulator import pulseSim2Q\n",
    "\n",
    "# Import the basis function to generate state bector\n",
    "from Quanlse.Utils.Functions import basis\n",
    "\n",
    "# Import the center-aligned pulse scheduling strategy\n",
    "from Quanlse.Superconduct.SchedulerSupport.PipelineCenterAligned import centerAligned\n",
    "\n",
    "# Import the two qubit gate calibration functions\n",
    "from Quanlse.Superconduct.Calibration.TwoQubit import czCaliCondPhase, czCaliDynamicalPhase, \\\n",
    "    czCaliCondPhaseJob, czCaliDynamicalPhaseJob, caliSingleQubitGates\n",
    "\n",
    "# Import the operator for generating basis string list\n",
    "from Quanlse.Utils.Functions import computationalBasisList, project\n",
    "\n",
    "# Import the function for plot bar figures\n",
    "from Quanlse.Utils.Plot import plotBarGraph\n",
    "\n",
    "# Import the QOperation\n",
    "from Quanlse.QOperation.FixedGate import H, CZ"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a67b2f5",
   "metadata": {},
   "source": [
    "## Initialize the Two-qubit Simulator\n",
    "\n",
    "First, we need to initialize a two-qubit simulator. In Quanlse v2.1, we have already added a two-qubit simulator template. We can instantiate a `Quanlse.Simulator.PulseModel` object through the `Quanlse.Simulator.PulseSim2Q()` function, where the parameter `dt` represents the time step length for solving the Schrödinger equation, and the `frameMode` represents the reference frame used for simulation (`lab` and `rot` represent the lab frame and rotating frame respectively):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f5581329",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sampling period.\n",
    "dt = 0.01\n",
    "\n",
    "# The indexes of qubits for calibration\n",
    "q0 = 0\n",
    "q1 = 1\n",
    "\n",
    "# Instantiate the simulator object by a 3-qubit template.\n",
    "model = pulseSim2Q(dt=dt, frameMode='lab')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b695164c",
   "metadata": {},
   "source": [
    "The simulator models two directly coupled three-level qubits with predefined properties such as qubit frequencies, anharmonicity strengths, and the coupling strength between the qubits. These information are stored in the instantiated object `model`, and can be accessed by:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3077a79",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Qubit frequency (GHz):\\n    \", model.qubitFreq)\n",
    "print(\"Microwave drive frequency (GHz):\\n    \", model.driveFreq)\n",
    "print(\"Qubit anharmonicity (GHz):\\n    \", model.qubitAnharm)\n",
    "print(\"Qubit coupling map (GHz):\\n    \", model.couplingMap)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "972411bd",
   "metadata": {},
   "source": [
    "For ease of use, the calibrated control pulse parameters are also included in `model`, which can be accessed by the user through the `model.conf` property:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7174560",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Microwave control pulse parameters (a.u.):\")\n",
    "print(f\"    q0: {model.conf['caliDataXY'][0]}\")\n",
    "print(f\"    q1: {model.conf['caliDataXY'][0]}\")\n",
    "print(\"Flux control pulse parameters (a.u.):\")\n",
    "print(f\"    q0: {model.conf['caliDataZ'][0]}\")\n",
    "print(f\"    q1: {model.conf['caliDataZ'][0]}\")\n",
    "print(\"CZ gate control pulse parameters (a.u.):\")\n",
    "print(f\"    q0q1: {model.conf['caliDataCZ'][(0, 1)]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "305e873f",
   "metadata": {},
   "source": [
    "The `pulseSim2Q()` method returns a pulse simulator type object `Quanlse.Simulator.PulseModel`, which inherits from class `Quanlse.Scheduler` ([click to view API](https://quanlse.baidu.com/Scheduler/Quanlse.Scheduler.html)), users can set the pulse scheduling strategy by themselves. Here we use the center-aligned strategy (`centerAligned`), and use the `addPipelineJob()` method in `model.pipeline` to add it to `model`, so that Quanlse Scheduler makes the pulses center-aligned:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9dda999b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set the center-aligned scheduling sctrategy\n",
    "model.pipeline.addPipelineJob(centerAligned)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bf7ffb1",
   "metadata": {},
   "source": [
    "Here, we set the property `model.savePulse` to `False` to turn off the pulse buffering for the quantum gates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d294351a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prevent Quanlse Scheduler to cache the pulses\n",
    "model.savePulse = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aafde0c3",
   "metadata": {},
   "source": [
    "## Pulse Calibration for Controlled-Z \n",
    "\n",
    "After initializing and configuring the simulation environment, we start the pulse calibration process for the CZ gate, which mainly includes the following steps:\n",
    "\n",
    "  1. Calibrate the Pulse for Single-qubit Gates\n",
    "  2. Calibrate the Conditional-phase\n",
    "  3. Calibrate the Dynamical-Phase"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e445672",
   "metadata": {},
   "source": [
    "### 1. Calibrate the Pulse for Single-qubit Gates\n",
    "\n",
    "Since the superconducting qubits are not ideal two-level systems in real experiments, for weak anharmonicity qubits, energy leakage to the third energy level can take the state of the qubit out of the computational subspace, so we need to consider the error introduced by energy leakage. In the [DRAG pulse](https://quanlse.baidu.com/#/doc/tutorial-drag) chapter, we have introduced the principle and method of correcting the waveform of the driving pulse to eliminate the error of energy level leakage. In this tutorial, we will also use DRAG pulses to improve the fidelity of single-qubit gates.\n",
    "\n",
    "In Quanlse v2.1, we provide a two-qubit calibration toolkit in the `Quanlse.Calibration.TwoQubit` module, in which we provide the function of DRAG pulse calibration in two-qubit systems, and users can use the `caliSingleQubitGates()` function to calibrate and obtain data of the calibrated pulses:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4a33cd4",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "q0ParaInit = [model.conf[\"caliDataXY\"][q0][\"piAmp\"], model.conf[\"caliDataXY\"][q0][\"piAmp\"]]\n",
    "q1ParaInit = [model.conf[\"caliDataXY\"][q1][\"dragCoef\"], model.conf[\"caliDataXY\"][q1][\"dragCoef\"]]\n",
    "bounds = [(0, 1), (-1, 1), (0, 1), (-1, 1)]\n",
    "\n",
    "q0PiAmp, q0Drag, q1PiAmp, q1Drag, optGatesLoss = caliSingleQubitGates(\n",
    "    model, q0, q1, bounds=bounds, q0ParaInit=q0ParaInit, q1ParaInit=q1ParaInit)\n",
    "\n",
    "print(f\"The optimal pi amp of q0 and q1 is {round(q0PiAmp, 6)} and {round(q1PiAmp, 6)}\")\n",
    "print(f\"The optimal DRAG coefficient of q0 and q1 is {round(q0Drag, 6)} and {round(q1Drag, 6)}\")\n",
    "print(f\"The minimal infidelity is {round(optGatesLoss, 6)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "612ec066",
   "metadata": {},
   "source": [
    "After completing the calibration of the $\\pi$ and DRAG pulse parameters, we add the calibrated $\\pi$ pulse amplitude and DRAG correction coefficient to `model.conf` through the following code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5cf9d86",
   "metadata": {},
   "outputs": [],
   "source": [
    "model.conf[\"caliDataXY\"][q0][\"piAmp\"] = q0PiAmp\n",
    "model.conf[\"caliDataXY\"][q0][\"dragCoef\"] = q0Drag\n",
    "model.conf[\"caliDataXY\"][q1][\"piAmp\"] = q1PiAmp\n",
    "model.conf[\"caliDataXY\"][q1][\"dragCoef\"] = q1Drag"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d5e98b6",
   "metadata": {},
   "source": [
    "### 2. Calibrate the Conditional phase\n",
    "\n",
    "In this section, we introduce how to calibrate the conditional phase, which is also the most important step to realize the CZ gate. We usually tune the frequency of each energy level through magnetic flux and change the phase of each quantum state $|ij\\rangle$, the corresponding matrix form is as follows:\n",
    "\n",
    "$$\n",
    "{\\rm CZ}_{\\rm real} = \\begin{bmatrix}\n",
    "1 & 0 & 0 & 0 \\\\ \n",
    "0 & e^{i\\theta_{01}} & 0 & 0 \\\\ \n",
    "0 & 0 & e^{i\\theta_{10}} & 0 \\\\ \n",
    "0 & 0 & 0 & e^{i\\theta_{11}}\n",
    "\\end{bmatrix}.\n",
    "$$\n",
    "\n",
    "Where $\\theta_{ij}$ represents the phase obtained by the quantum state $|ij\\rangle$. To implement the CZ gate, we first need to implement the conditional phase as $\\pi$, that is to say, design the flux control trajectory and make $\\theta_{11}=\\pi$. The method is as follows: first, we apply an $X/2$ gate on the first qubit q0 to prepare it in the superposition state of $|0\\rangle$ and $|1\\rangle$; and simultaneously, apply an $X$ gate or $I$ gate on the second qubit q1 respectively; then, apply flux control to realize $ |11\\rangle$ phase accumulation; finally execute an $X/2$ gate on q0 to change the coordinate representation and display the phase change, as shown in the following figure:\n",
    "\n",
    "![fig:czCali_circuit](figures/cali-cz-circuit.png)\n",
    "\n",
    "Where $\\alpha_0$ and $\\alpha_1$ are the magnetic flux tuning the first and second qubits, respectively. Through the measurement, we can obtain the final state when q0 is $|0\\rangle$ or $|1\\rangle$ state respectively:\n",
    "\n",
    "$$\n",
    "\\left[R_x(\\pi/2)\\otimes I\\right] \\cdot |\\psi\\rangle_{\\rm q1=|0\\rangle} = \\frac{1-e^{i\\theta_{10}}}{2} |00\\rangle - \\frac{i(1+e^{i\\theta_{10}})}{2} |10\\rangle, \\\\\n",
    "\\left[R_x(\\pi/2)\\otimes I\\right] \\cdot |\\psi\\rangle_{\\rm q1=|1\\rangle} = \\frac{e^{i\\theta_{01}}-e^{i\\theta_{11}}}{2} |01\\rangle - \\frac{i(e^{i\\theta_{01}}+e^{i\\theta_{11}})}{2} |11\\rangle.\n",
    "$$\n",
    "\n",
    "It is obvious that when the $I$ gate is applied to q1, with $\\theta_{10}=0$, the final state is $-i|10\\rangle$, hence the measurement of q0 should give more counts of $|1 \\rangle$. And when $X$ gate is applied on q1, if $\\theta_{11}=\\pi$ and $\\theta_{01}=0$, $R_x(\\pi/2) \\cdot |\\psi \\rangle_{\\rm q1=|1\\rangle}=|01\\rangle$, hence measuring q0 should give more counts of $|0\\rangle$. Therefore, we can optimize the measurement result of q0 as a loss function to obtain the required conditional phase:\n",
    "\n",
    "$$\n",
    "{\\rm Loss} = {\\rm Prob_{q1=|0\\rangle}(|01\\rangle+|11\\rangle)} + {\\rm Prob_{q1=|1\\rangle}(|00\\rangle+|10\\rangle)}.\n",
    "$$\n",
    "\n",
    "We also encapsulate the conditional phase calibration method `czCaliCondPhase()` in the `Quanlse.Calibration.TwoQubit` module. This method contains functions such as pulse scheduling, measurement and optimization and users can directly obtain the calibrated by using it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4202e614",
   "metadata": {},
   "outputs": [],
   "source": [
    "optQ0ZAmp, optQ1ZAmp, optCZLen, optCondPhaseLoss = czCaliCondPhase(\n",
    "    sche=model, q0=q0, q1=q1, maxIter=50)\n",
    "\n",
    "print(f\"The optimal loss value is {optCondPhaseLoss}\")\n",
    "print(f\"The optimal amplitude of Z pulse on qubit {q0} is {optQ0ZAmp}\")\n",
    "print(f\"The optimal amplitude of Z pulse on qubit {q1} is {optQ1ZAmp}\")\n",
    "print(f\"The optimal amplitude of duration of Z pulses is {optCZLen} ns\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42e7b554",
   "metadata": {},
   "source": [
    "Next, we modify the pulse length `czLen` and the Z pulse amplitudes of the first and second qubit `q0ZAmp`, `q1ZAmp` in the configuration of `model` for later use:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98da5191",
   "metadata": {},
   "outputs": [],
   "source": [
    "model.conf[\"caliDataCZ\"][(0, 1)][\"q0ZAmp\"] = optQ0ZAmp\n",
    "model.conf[\"caliDataCZ\"][(0, 1)][\"q1ZAmp\"] = optQ1ZAmp\n",
    "model.conf[\"caliDataCZ\"][(0, 1)][\"czLen\"] = optCZLen"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12905c5c",
   "metadata": {},
   "source": [
    "Here we plot the pulse sequences for calibration:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17b291df",
   "metadata": {},
   "outputs": [],
   "source": [
    "condPhaseJobList = czCaliCondPhaseJob(model, q0, q1, optQ0ZAmp, optQ1ZAmp, optCZLen)\n",
    "print(r\"When the second qubit is initialized to |1>:\")\n",
    "condPhaseJobList.jobs[0].plot()\n",
    "print(r\"When the second qubit is initialized to |0>:\")\n",
    "condPhaseJobList.jobs[1].plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea303a6d",
   "metadata": {},
   "source": [
    "### 3. Calibrate the Dynamical-Phase\n",
    "\n",
    "In the above steps, we apply the magnetic flux to generate the conditional phase of $\\pi$. But at the same time, the applied magnetic flux will also produce dynamical phase accumulation on each qubit, so in this section, we aim to design the control pulses to compensate for the dynamical phase.\n",
    "\n",
    "Here, we use the virtual-$Z$ (VZ) gate to achieve the above compensation. The basic principle of the VZ gate is to adjust the phase of the arbitrary wave generator (AWG) to realize the operation similar to the rotation along the $z$-axis. For example, we want to perform two successive $X_{\\theta}$ operations, however, the second X operation has a phase $\\phi$ shift with respect to the first $X$ operation, i.e.:\n",
    "\n",
    "$$\n",
    "X^{(\\phi_0)}_{\\theta} X_{\\theta} = e^{-i\\theta(\\hat{\\sigma}_x\\cos\\phi_0+\\hat{\\sigma}_y\\sin\\phi_0) / 2} X_{\\theta} = Z_{-\\phi_0}X_{\\theta}Z_{\\phi_0}X_{\\theta}.\n",
    "$$\n",
    "\n",
    "Since the measurement of the qubit in the superconducting system is carried out along the $z$-axis, this makes the final $Z_{-\\phi_0}$ operation has no effect on the observables. Therefore, it can be seen that the effect of adjusting the AWG phase is equivalent to adding Z pulse between the two X gates.\n",
    "\n",
    "In this tutorial, we use the following circuit to achieve pulse calibration:\n",
    "\n",
    "![fig:czCali_dynamical_phase_circuit](figures/cali-cz-dynamics-phase.png)\n",
    "\n",
    "Where $Z_{\\theta_1}$ and $Z_{\\theta_2}$ are implemented using VZ gates. According to the above quantum circuit and the values of $\\theta_1$ and $\\theta_2$, Quanlse Scheduler is used to prepare the required pulse sequence. We use VZ gates to implement $Z_{\\theta_1}$ and $Z_{\\theta_2}$. Subsequently, we calculate the evolution simulation result, and use the infidelity between the final state and the ideal Bell state $(|00\\rangle + |11\\rangle) / \\sqrt{2}$ as the loss function for optimization. Similarly, Quanlse 2.1 encapsulates the above functions. Users can directly call the `czCaliDynamicalPhase()` function in the `Quanlse.Calibration.TwoQubit` module to calibrate the dynamical phase, which will output the optimal phase shift amount $\\theta_1^*$ and $\\theta_2^*$, as well as the infidelity of the ideal Bell state:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "815d7437",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "optQ0VZPhase, optQ1VZPhase, optDynaPhaseLoss = czCaliDynamicalPhase(\n",
    "    sche=model, q0=q0, q1=q1, method=\"Nelder-Mead\", q0VZPhaseInit=0., q1VZPhaseInit=0.)\n",
    "\n",
    "print(f\"The optimal loss value is {optDynaPhaseLoss}\")\n",
    "print(f\"The optimal phase correction on qubit {q0} is {optQ0VZPhase / 2 / pi} * 2pi\")\n",
    "print(f\"The optimal phase correction on qubit {q1} is {optQ1VZPhase / 2 / pi} * 2pi\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3967245e",
   "metadata": {},
   "source": [
    "It is worth noting that in the above steps, we can use techniques such as Randomized benchmarking or Quantum process tomography to replace the method of calculating Bell distortion in step 3 above to optimize the phase to obtain more accurate results.\n",
    "\n",
    "Finally, we store the calibrated phase information in `model`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e8f193db",
   "metadata": {},
   "outputs": [],
   "source": [
    "model.conf[\"caliDataCZ\"][(0, 1)][\"q0VZPhase\"] = optQ0VZPhase\n",
    "model.conf[\"caliDataCZ\"][(0, 1)][\"q1VZPhase\"] = optQ1VZPhase"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82f37c43",
   "metadata": {},
   "source": [
    "## Generating Bell State Using Calibrated Pulses\n",
    "\n",
    "With the previous steps, the pulses required for the CZ gate have been calibrated. Now, we can use the calibrated waveforms to compile a given quantum circuit. Users can use all the features of **Quanlse Scheduler** directly through the `model` object. We can first use the `model.clearCircuit()` method to clear the defined quantum circuit in the current model. Then add the quantum circuit to prepare the Bell state, and call the `model.schedule()` method to compile and generate the required pulse sequences. Here, the compilation process will call the previously saved pulse parameters to generate the pulses, thereby generating a pulse sequence with higher fidelity:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c67090a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Clear the circuit\n",
    "model.clearCircuit()\n",
    "\n",
    "# Define the circuit\n",
    "H(model.Q[0])\n",
    "H(model.Q[1])\n",
    "CZ(model.Q[0], model.Q[1])\n",
    "H(model.Q[1])\n",
    "\n",
    "# Generate the ideal unitary of the quantum circuit\n",
    "uIdeal = model.getMatrix()\n",
    "\n",
    "# Generate the pulse for the circuit\n",
    "jobBell = model.schedule()\n",
    "jobBell.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98134c22",
   "metadata": {},
   "source": [
    "Then we can use the `model.simulate()` method and pass in the pulse task `jobBell`, the initial state and the number of repetitions to simulate evolution, and get the count of each ground state in the final state:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "afabfea9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculate final state\n",
    "finalState = model.simulate(\n",
    "    job=jobBell, state0=basis(model.sysLevel ** model.subSysNum, 0), shot=1000)\n",
    "\n",
    "# Print the population distance of Bell State\n",
    "pop = project(numpy.square(abs(finalState[0][\"state\"])).T[0], model.subSysNum, model.sysLevel, 2)\n",
    "stateIdeal = uIdeal @ basis(uIdeal.shape[0], 0).T[0]\n",
    "popIdeal = numpy.square(abs(stateIdeal))\n",
    "print(\"Distance of real and ideal Bell states:\", numpy.sum(numpy.abs(pop - popIdeal)) / len(pop))\n",
    "\n",
    "# Plot the population of computational basis\n",
    "plotBarGraph(computationalBasisList(2, 3), finalState[0][\"population\"], \n",
    "             \"Counts of the computational basis\", \"Computational Basis\", \"Counts\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d16afce",
   "metadata": {},
   "source": [
    "Here, we generate a high-fidelity Bell state with the calibrated single-qubit gates and CZ gates. In real quantum computers, we can further improve the fidelity of CZ gates utilizing quantum process tomography or randomized benchmarking technologies."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f311af0",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "After reading this tutorial on CZ gate pulse calibration, the users could follow this link [tutorial-calibration-cz.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/EN/tutorial-calibration-cz.ipynb) to the GitHub page of this Jupyter Notebook document and obtain the relevant code to run this program for themselves. The users are encouraged to explore other advanced research which is different from this tutorial.\n",
    "\n",
    "## References\n",
    "\n",
    "\\[1\\] [Krantz, Philip, et al. \"A quantum engineer's guide to superconducting qubits.\" *Applied Physics Reviews* 6.2 (2019): 021318.](https://aip.scitation.org/doi/abs/10.1063/1.5089550)\n",
    "\n",
    "\\[2\\] [Yuan, Xu, et al. \"High-Fidelity, High-Scalability Two-Qubit Gate Scheme for Superconducting Qubits.\" *Physical Review Letters* 125 (2020): 240503 .](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.125.240503)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}